import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.ArrayList;

/**
 * 
 * Moises Florez and Peter Matano
 *
 */
public class OS implements OperatingSystem {
	private Hardware simHW;
	private DiskEntity dEnt;
	private ProgramEntity proEnt;	
		
	int ttyData = 1;
		
	boolean terminalInUse;
	private Stack<Integer> terminalStack;
	
	private List<ConnectionDetails> connectionList;
		
	private int terminalDataStartAddress = 0;
	private int numberOfCharToRead = 0;
	
	private int countdown = 30000;
	private int clock = countdown;
		
	private RoundRobinScheduler processScheduler;
	private List<Process> processQueque;
	private int receiverId;
	private int slotValue;
	
	private BlockReadData userSpaceLastBlockReadData;
	private BlockReadWriteDetails userSpaceLastBlockReadWriteDetail;	
	private List<BlockReadWriteDetails> userSpaceBlockReadWriteDetailList;
	private MemoryManager userSpaceMemManager;
		
	private BlockReadData systemSpaceLastBlockReadData;	
	private BlockReadWriteDetails systemSpaceLastBlockReadWriteDetail;
	private List<BlockReadWriteDetails> systemSpaceBlockReadWriteDetailList;
	private MemoryManager systemSpaceMemManager;	
	
	private BlockReadData deviceSpaceLastBlockReadData;	
	private BlockReadWriteDetails deviceSpaceLastBlockReadWriteDetail;
	private List<BlockReadWriteDetails> deviceSpaceBlockReadWriteDetailList;
	private MemoryManager deviceSpaceMemManager;
	
	private int userSpaceNextBlock;	
	private int systemSpaceNextBlock;	
		
	public OS(Hardware hw) {
		simHW = hw; // Set simulator hardware.
		proEnt = new ProgramEntity();
		dEnt = new DiskEntity();
		processQueque = new ArrayList<Process>();
		processScheduler = new RoundRobinScheduler(processQueque, clock);
		
		terminalStack = new Stack<Integer>();
		connectionList = new ArrayList<ConnectionDetails>();
		
		userSpaceLastBlockReadData = new BlockReadData();
		userSpaceBlockReadWriteDetailList = new ArrayList<BlockReadWriteDetails>();
		userSpaceLastBlockReadWriteDetail = new BlockReadWriteDetails();
		userSpaceMemManager = new MemoryManager(userSpaceBlockReadWriteDetailList);
		
		systemSpaceBlockReadWriteDetailList = new ArrayList<BlockReadWriteDetails>();
		systemSpaceLastBlockReadWriteDetail = new BlockReadWriteDetails();
		systemSpaceLastBlockReadData = new BlockReadData();					
		systemSpaceMemManager = new MemoryManager(systemSpaceBlockReadWriteDetailList);
		
		deviceSpaceBlockReadWriteDetailList = new ArrayList<BlockReadWriteDetails>();
		deviceSpaceLastBlockReadWriteDetail = new BlockReadWriteDetails();
		deviceSpaceLastBlockReadData = new BlockReadData();					
		deviceSpaceMemManager = new MemoryManager(deviceSpaceBlockReadWriteDetailList);				
	}
	
	/**
	 * Get the next available block from memory manager.
	 * @return
	 */
	public int getUserSpaceNextBlock() {
		userSpaceNextBlock = userSpaceMemManager.getNextBlock();
		return userSpaceNextBlock;
	}	

	public int getSystemSpaceNextBlock() {
		systemSpaceNextBlock = systemSpaceMemManager.getNextBlock();
		return systemSpaceNextBlock;
	}
	
	private void holtOS(){
		simHW.store(Hardware.Address.haltRegister, 2);
	}
	
	/**
	 * OS Interrupt handler 
	 */
	@Override
	public void interrupt(Hardware.Interrupt it) {
		switch (it) {
		case illegalInstruction:
			printLine("Interrupt: illegalInstruction");
			simHW.store(Hardware.Address.haltRegister, 2);
			break;	
		case reboot:
			printLine("Interrupt: reboot");
			// Load the disk to primary store one block at the time.
			
			int blockIndex = 1;
			int userBaseStartAddress = Hardware.Address.userBase;
			int systemBaseStartAddress = Hardware.Address.systemBase + (blockIndex * 32);
			int deviceBaseStartAddress = Hardware.Address.deviceBase + (blockIndex * 32);
			/*
			 * Start tracking read information.
			 */
			userSpaceLastBlockReadData.setBlockNumber(0); // Save it before it is incremented.
			userSpaceLastBlockReadData.setBlockAddress(userBaseStartAddress); // Start @ user base.
			userSpaceLastBlockReadData.setReadStarted(true); // The read started.  Disk interrupt will be triggered.
			userSpaceLastBlockReadData.setProgramsStarted(false); // Programs started.
			
			simHW.store(Hardware.Address.diskBlockRegister, getUserSpaceNextBlock());
			simHW.store(Hardware.Address.diskAddressRegister, userBaseStartAddress);
			simHW.store(Hardware.Address.diskCommandRegister, Hardware.Disk.readCommand);
			
			/*
			 * Start tracking system space information
			 */
			systemSpaceLastBlockReadData.setBlockNumber(blockIndex); // Save it before it is incremented.
			systemSpaceLastBlockReadData.setBlockAddress(systemBaseStartAddress); // Start @ user base.
			systemSpaceLastBlockReadData.setReadStarted(true); // The read started.  Disk interrupt will be triggered.
			systemSpaceLastBlockReadData.setProgramsStarted(false); // Programs started.
			
			/*
			 * Start tracking system space information
			 */
			deviceSpaceLastBlockReadData.setBlockNumber(blockIndex); // Save it before it is incremented.
			deviceSpaceLastBlockReadData.setBlockAddress(deviceBaseStartAddress); // Start @ user base.
			deviceSpaceLastBlockReadData.setReadStarted(true); // The read started.  Disk interrupt will be triggered.
			deviceSpaceLastBlockReadData.setProgramsStarted(false); // Programs started.
			
			/*
			 * Start idle
			 */
			simHW.store(Hardware.Address.PCRegister, Hardware.Address.idleStart);//Set PCRegister to prevent illegal instruction interrupt
			break;
		case systemCall:
			int sysCallVal = simHW.fetch(Hardware.Address.systemBase);
			printLine("Interrupt: systemCall(" + sysCallVal + ")");
			operatingSystemCall(sysCallVal);
			break;		
		case invalidAddress:
			printLine("Interrupt: invalidAddress");
			holtOS();
			break;			
		case disk:
			printLine("Interrupt: disk");			
			int exeProgramsBlockCount = getExeProgsBlockCount();  // total programs block count.
			
			if(exeProgramsBlockCount == 0) //If disk is empty then halt OS
			{
				simHW.store(Hardware.Address.haltRegister, 2);
			}				
			
			if (userSpaceLastBlockReadData.isProgramLoadingComplete() == false) {
				int loadBlockCount = exeProgramsBlockCount + 1; // Load 1 extra block for indexBlock.  Should be one more than program locks
				if (userSpaceLastBlockReadData.getBlockNumber() < loadBlockCount) {
					loadNextDiskBlock(); // Load the next disk block.
					simHW.store(Hardware.Address.PCRegister, Hardware.Address.idleStart);//Set PCRegister to prevent illegal instruction interrupt
				} else if (userSpaceLastBlockReadData.getBlockNumber() == loadBlockCount) { // Program block loaded.
					
					userSpaceLastBlockReadData.setProgramLoadingComplete(true); // will only run once.
					this.createDiskEntity(); // Create the disk entity blocks.
				
					List<WordEntity> iEntity = dEnt.getBlockEntity(0).getWordEntityList();
					this.queueProcessExecution(iEntity);	
					simHW.store(Hardware.Address.countdownRegister, countdown); // Set a timer to start program execution.
				
					printLine("First program started...");
					int proIndex = 0; // Call the first program.
					processProgramExeRequest(proIndex); // Implements Round Robin.  It starts processing preemptively based on the next on the list and the count down timer.
					userSpaceLastBlockReadData.setProgramsStarted(true); // All programs where started					
				}
			}			
			break;
		case terminal:
			printLine("Interrupt: terminal");					
								
			int data = this.simHW.fetch(Hardware.Address.terminalDataRegister);
			printLine("Terminal Data: " + data);	
				
			int connectionID; // 1 is device, 3 is terminal.		
			connectionID = this.simHW.fetch(Hardware.Address.systemBase + 1); // Word 1 (1 is drive, 3 is terminal)
			
			int readToAddress = this.simHW.fetch(Hardware.Address.systemBase + 2); // Word 2
			printLine("executeDeviceReadCall->Terminal (readToAddress): Word 2: " + readToAddress);
		
			int nValue = this.simHW.fetch(Hardware.Address.systemBase + 3); // Word 3
			numberOfCharToRead = nValue;
			printLine("executeDeviceReadCall->Terminal (nValue): Word 3: " + nValue);
			
			int status = this.simHW.fetch(Hardware.Address.terminalStatusRegister);
			if(status == Hardware.Status.ok)
			{
				printLine("Terminal: Hardware.Status.ok");
				if (numberOfCharToRead > 0){
					printLine("terminalDataStartAddress: " + terminalDataStartAddress);
					int terminalData = this.simHW.fetch(terminalDataStartAddress);
					printLine("terminalData: " + terminalData);
					ttyData = this.simHW.fetch(Hardware.Address.terminalDataRegister); // Copy the data from the tty data registry
										
					this.simHW.store(terminalDataStartAddress, ttyData);
					this.simHW.store(Hardware.Address.terminalDataRegister, ttyData);
					
					printLine("numberOfChrToRead: " + numberOfCharToRead);					
				}
				
			} else if (status == Hardware.Status.badCommand)
			{
				printLine("Terminal: Hardware.Status.badCommand");
			}			
			break;
		case countdown:
			int cDownReg = simHW.fetch(Hardware.Address.countdownRegister);
			if (cDownReg == 0) { // Count down finished reset to stop it from hanging.
				this.simHW.store(Hardware.Address.countdownRegister, countdown); // set a new count down to keep system from hanging.
				simHW.store(Hardware.Address.PCRegister, Hardware.Address.idleStart);//Set PCRegister to prevent illegal instruction interrupt
			}			
			// executeProcessScheduling();			
			break;
		}
	}

	/**
	 * Process control block scheduling.
	 */
	private void executeProcessScheduling(){
		do {
			clock++;
			processScheduler.T = processScheduler.processready(clock);
			if (processScheduler.T != null) {
				processScheduler.readyQueue.add(processScheduler.T);
				processScheduler.queue.remove(processScheduler.T);
				printLine("Time " + processScheduler.T.getArrival() + ":Process " + processScheduler.T.getProcessId() + " ready.");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
				}
				;
			} // put in ready queue
			if (processScheduler.idle || processScheduler.interrupt) {
				if (processScheduler.interrupt) {
					processScheduler.interrupt = false;
					processScheduler.interval = 0;
				} // reset interval
				if (processScheduler.readyQueue.size() == 0)
					continue;
				if (processScheduler.next < processScheduler.readyQueue.size() - 1)
					processScheduler.next++;
				else
					processScheduler.next = 0;
				processScheduler.P = (Process) processScheduler.readyQueue.get(processScheduler.next);
				processScheduler.idle = false;
			} // get next process
			processScheduler.P.servicing();
			processScheduler.interval++;
			
			printLine("Time " + clock + ":Serving process " + processScheduler.P.getProcessId() + ".");
							
			if (processScheduler.P.getTminus() == 0) {
				printLine("Time " + (clock + 1) + ":Process " + processScheduler.P.getProcessId() + " done.");
				
				this.simHW.store(Hardware.Address.countdownRegister, -1); // Reset countdown...
				this.simHW.store(Hardware.Address.countdownRegister, countdown); // set a new count down to keep system from hanging.
									
				processScheduler.P.report(clock + 1); // anticipate completion
				processScheduler.finishQueue.add(processScheduler.P);
				processScheduler.readyQueue.remove(processScheduler.P);
				processScheduler.next--; // recent pointer.
				processScheduler.idle = true;
				processScheduler.interval = 0;
			} // put in finish queue
			else if (processScheduler.interval == 4)
				processScheduler.interrupt = true;
		} while (processScheduler.finishQueue.size() < processScheduler.all);
		
	}
	
	/**
	 * Loads the next disk block.
	 */
	private void loadNextDiskBlock(){		
		int nextBlockStartAddress = simHW.fetch(Hardware.Address.diskAddressRegister) + 32; //Find where to load next block
		int nextBlockIndex = getUserSpaceNextBlock();
		
		this.simHW.store(Hardware.Address.diskBlockRegister, nextBlockIndex);//Next block from disk   			
		this.simHW.store(Hardware.Address.diskAddressRegister, nextBlockStartAddress);//Set next block start address			
		this.simHW.store(Hardware.Address.diskCommandRegister, Hardware.Disk.readCommand);//Read from disk to primary storage
						
		/*
		 * Keep track of blocks written to user space.
		 */
		userSpaceLastBlockReadData.setBlockAddress(nextBlockStartAddress); 
		userSpaceLastBlockReadData.setBlockNumber(nextBlockIndex);
		userSpaceLastBlockReadData.setReadStarted(true);
		
		/*
		 * Save the blocks and store them in a list.
		 */
		userSpaceLastBlockReadWriteDetail.setBlockReadData(userSpaceLastBlockReadData); 
		userSpaceMemManager.getBlockReadWriteDetailList().add(userSpaceLastBlockReadWriteDetail);				
	}		
	
	/**
	 * Reads the requested disk block.  This action will trigger the disk interrupt.
	 * @param blockNumber
	 * @param readToAddress
	 */
	private void readDiskBlockFromDevice(int blockNumber, int readToAddress) {
		printLine("readDiskBlockFromDevice(blockNumber:[" + blockNumber + "], readToAddress:[" + readToAddress + "])");
		/*
		 * I has not reached 32; track it with MemoryManager.
		 */
		int nextUserSpaceAvailableBlockIndex = userSpaceLastBlockReadData.getBlockNumber() + 1;
		int nextUserSpaceAvailableBlockAddress = userSpaceLastBlockReadData.getBlockAddress() + 32;

		int nextSystemSpaceAvailableBlockIndex = systemSpaceLastBlockReadData.getBlockNumber() + 1;
		int nextSystemSpaceAvailableBlockAddress = systemSpaceLastBlockReadData.getBlockAddress() + 32;

		int nextDeviceSpaceAvailableBlockIndex = deviceSpaceLastBlockReadData.getBlockNumber() + 1;
		int nextDeviceSpaceAvailableBlockAddress = deviceSpaceLastBlockReadData.getBlockAddress() + 32;

		/*
		 * Search for the block using the BlockReadData. If it is found the
		 * block is in memory, and if not found add it to MemoryManager.
		 */
		boolean foundBlock = this.userSpaceMemManager.isBlockInReadWriteBlockDetailsList(blockNumber, readToAddress);
		printLine("foundBlock: " + foundBlock);
		if (foundBlock == false) {
			printLine("foundBlock: " + foundBlock);

			printLine("nextAvailableBlockIndex: " + nextUserSpaceAvailableBlockIndex);
			printLine("nextAvailableBlockAddress: " + nextUserSpaceAvailableBlockAddress);

			if (nextUserSpaceAvailableBlockIndex < 32) {

				/*
				 * The block was not found in manager; it needs to be added to
				 * the next block in user space and added to MemoryManager.
				 */
				int nxtUsrSpaceBlkIdx = this.getUserSpaceNextBlock();
				loadBlockToPrimaryStore(nxtUsrSpaceBlkIdx, nextUserSpaceAvailableBlockAddress);

				/*
				 * Add to user space memory management.
				 */
				addUserBaseBlockToMemoryManager(nextUserSpaceAvailableBlockIndex, nextUserSpaceAvailableBlockAddress, blockNumber, readToAddress);
			} else if (nextUserSpaceAvailableBlockIndex == 32) {

				if (nextSystemSpaceAvailableBlockAddress < 32) {
					printLine("nextSystemSpaceAvailableBlockIndex: " + nextSystemSpaceAvailableBlockIndex);
					printLine("nextSystemSpaceAvailableBlockAddress: " + nextSystemSpaceAvailableBlockAddress);

					loadBlockToPrimaryStore(nextSystemSpaceAvailableBlockIndex, nextSystemSpaceAvailableBlockAddress);

					printLine("");
					printLine("");
					printLine("nextSystemSpaceAvailableBlockIndex: " + nextSystemSpaceAvailableBlockIndex);
					printLine("nextSystemSpaceAvailableBlockAddress: " + nextSystemSpaceAvailableBlockAddress);
					printLine("");
					printLine("");

					/*
					 * Add to system space memory management.
					 */
					addSystemBaseBlockToMemoryManager(nextSystemSpaceAvailableBlockAddress, nextSystemSpaceAvailableBlockAddress, blockNumber, readToAddress);

					/*
					 * Save the settings to allow an increment next go around.
					 */
					systemSpaceLastBlockReadData.setBlockNumber(nextSystemSpaceAvailableBlockIndex);
					systemSpaceLastBlockReadData.setBlockAddress(nextSystemSpaceAvailableBlockAddress);
				}
			} else if (nextSystemSpaceAvailableBlockAddress == 32) {

				if (nextDeviceSpaceAvailableBlockAddress < 32) {

					printLine("nextDeviceSpaceAvailableBlockIndex: " + nextDeviceSpaceAvailableBlockIndex);
					printLine("nextDeviceSpaceAvailableBlockAddress: " + nextDeviceSpaceAvailableBlockAddress);

					loadBlockToPrimaryStore(nextSystemSpaceAvailableBlockIndex, nextDeviceSpaceAvailableBlockAddress);

					printLine("");
					printLine("");
					printLine("nextDeviceSpaceAvailableBlockIndex: " + nextDeviceSpaceAvailableBlockIndex);
					printLine("nextDeviceSpaceAvailableBlockAddress: " + nextDeviceSpaceAvailableBlockAddress);
					printLine("");
					printLine("");

					/*
					 * Add to system space memory management.
					 */
					addDeviceBaseBlockToMemoryManager(nextDeviceSpaceAvailableBlockAddress, nextDeviceSpaceAvailableBlockAddress, blockNumber, readToAddress);

				}
			}

		} else {
			/*
			 * The memory block was found in memory already; it should be
			 * reloaded from device.
			 */
			printLine("Info: Out of Memory!!!!!!!!...:");
		}
	}
	
	/**
	 * Add block to next available user space block. Read from disk to primary storage 32 addresses at the time.
	 * @param blockNumber
	 * @param blockAddress
	 */
	private void loadBlockToPrimaryStore(int blockNumber, int blockAddress){
		this.simHW.store(Hardware.Address.diskBlockRegister, blockNumber);  			
		this.simHW.store(Hardware.Address.diskAddressRegister, blockAddress);			
		this.simHW.store(Hardware.Address.diskCommandRegister, Hardware.Disk.readCommand);
	}	

	/**
	 * Add memory block to the user space memory manager for tracking.
	 * @param nextAvailableBlockIndex
	 * @param nextAvailableBlockAddress
	 * @param blockNumber
	 * @param readToAddress
	 */
	private void addUserBaseBlockToMemoryManager(int nextAvailableBlockIndex, int nextAvailableBlockAddress, int blockNumber, int readToAddress){
		/*
		 * Track last block read information.
		 */	
		userSpaceLastBlockReadData.setBlockNumber(nextAvailableBlockIndex);
		userSpaceLastBlockReadData.setBlockAddress(nextAvailableBlockAddress);
		userSpaceLastBlockReadData.setReadStarted(true);		
		userSpaceLastBlockReadWriteDetail.setBlockReadData(userSpaceLastBlockReadData);
		
		/*
		 * Block source information.  it will be used to write back to the disk.
		 */
		BlockWriteData blockWriteData = new BlockWriteData();
		blockWriteData.setBlockNumber(blockNumber);
		blockWriteData.setBlockAddress(readToAddress);
		
		userSpaceLastBlockReadWriteDetail.setBlockReadData(userSpaceLastBlockReadData); 
		userSpaceLastBlockReadWriteDetail.setBlockWriteData(blockWriteData);
		
		userSpaceMemManager.getBlockReadWriteDetailList().add(userSpaceLastBlockReadWriteDetail);
	}
	
	/**
	 * Add memory block to the system space memory manager for tracking.
	 * @param nextAvailableBlockIndex
	 * @param nextAvailableBlockAddress
	 * @param blockNumber
	 * @param readToAddress
	 */
	private void addSystemBaseBlockToMemoryManager(int nextAvailableBlockIndex, int nextAvailableBlockAddress, int blockNumber, int readToAddress){
		/*
		 * Track last block read information.
		 */	
		systemSpaceLastBlockReadData.setBlockNumber(nextAvailableBlockIndex);
		systemSpaceLastBlockReadData.setBlockAddress(nextAvailableBlockAddress);
		systemSpaceLastBlockReadData.setReadStarted(true);		
		systemSpaceLastBlockReadWriteDetail.setBlockReadData(systemSpaceLastBlockReadData);
		printLine("Info: systemSpaceProcessing...");
		/*
		 * Block source information.  it will be used to write back to the disk.
		 */
		BlockWriteData blockWriteData = new BlockWriteData();
		blockWriteData.setBlockNumber(blockNumber);
		blockWriteData.setBlockAddress(readToAddress);
		
		systemSpaceLastBlockReadWriteDetail.setBlockReadData(systemSpaceLastBlockReadData); 
		systemSpaceLastBlockReadWriteDetail.setBlockWriteData(blockWriteData);
		
		systemSpaceMemManager.getBlockReadWriteDetailList().add(systemSpaceLastBlockReadWriteDetail);
	}
	
	private void addDeviceBaseBlockToMemoryManager(int nextAvailableBlockIndex, int nextAvailableBlockAddress, int blockNumber, int readToAddress){
		/*
		 * Track last block read information.
		 */	
		deviceSpaceLastBlockReadData.setBlockNumber(nextAvailableBlockIndex);
		deviceSpaceLastBlockReadData.setBlockAddress(nextAvailableBlockAddress);
		deviceSpaceLastBlockReadData.setReadStarted(true);		
		deviceSpaceLastBlockReadWriteDetail.setBlockReadData(deviceSpaceLastBlockReadData);
		printLine("Info: deviceSpaceProcessing...");
		/*
		 * Block source information.  it will be used to write back to the disk.
		 */
		BlockWriteData blockWriteData = new BlockWriteData();
		blockWriteData.setBlockNumber(blockNumber);
		blockWriteData.setBlockAddress(readToAddress);
		
		deviceSpaceLastBlockReadWriteDetail.setBlockReadData(deviceSpaceLastBlockReadData); 
		deviceSpaceLastBlockReadWriteDetail.setBlockWriteData(blockWriteData);
		
		deviceSpaceMemManager.getBlockReadWriteDetailList().add(deviceSpaceLastBlockReadWriteDetail);
	}
	/**
	 * Write request disk block
	 * @param blockNumber
	 * @param writeFromAddress
	 */
	private void writeDiskBlockToDevice(int blockNumber, int writeFromAddress) {
		if (writeFromAddress > Hardware.Address.userBase && writeFromAddress < Hardware.Address.deviceBase) {
			printLine("Info UserSpace: writeDiskBlockToDevice(int blockNumber, int writeFromAddress)");
			BlockWriteData blkWrDt = new BlockWriteData();
			blkWrDt.setBlockNumber(blockNumber);
			blkWrDt.setBlockAddress(writeFromAddress);

			BlockReadWriteDetails rwDt = this.userSpaceMemManager.findReadWriteBlockDetails(blkWrDt);
			if (rwDt != null) {
				printLine("Info UserSpace: Block Found In Memory Manager: " + writeFromAddress);

				int targetDiskBlockNumber = rwDt.getBlockWriteData().getBlockNumber();
				int targetDiskBlockAddress = rwDt.getBlockWriteData().getBlockAddress();

				this.simHW.store(Hardware.Address.diskBlockRegister, targetDiskBlockNumber);
				this.simHW.store(Hardware.Address.diskAddressRegister, targetDiskBlockAddress);
				this.simHW.store(Hardware.Address.diskCommandRegister, Hardware.Disk.writeCommand);
			} else {
				printLine("Info: Block Not Found In UserSpace Memory Manager: " + writeFromAddress);
			}
		} else if (writeFromAddress > Hardware.Address.systemBase && writeFromAddress < Hardware.Address.userBase) {
			printLine("Info: writeDiskBlockToDevice(int blockNumber, int writeFromAddress)");
			BlockWriteData blkWrDt = new BlockWriteData();
			blkWrDt.setBlockNumber(blockNumber);
			blkWrDt.setBlockAddress(writeFromAddress);

			BlockReadWriteDetails rwDt = this.systemSpaceMemManager.findReadWriteBlockDetails(blkWrDt);
			if (rwDt != null) {
				printLine("Info SystemSpace: Block Found In SystemSpace Memory Manager: " + writeFromAddress);
				int targetDiskBlockNumber = rwDt.getBlockWriteData().getBlockNumber();
				int targetDiskBlockAddress = rwDt.getBlockWriteData().getBlockAddress();

				this.simHW.store(Hardware.Address.diskBlockRegister, targetDiskBlockNumber);
				this.simHW.store(Hardware.Address.diskAddressRegister, targetDiskBlockAddress);
				this.simHW.store(Hardware.Address.diskCommandRegister, Hardware.Disk.writeCommand);
			} else {
				printLine("Info SystemSpace: Block Not Found In SystemSpace Memory Manager: " + writeFromAddress);
			}
		} else if (writeFromAddress > Hardware.Address.userBase && writeFromAddress < Hardware.Address.deviceTop) {
			printLine("Info DeviceSpace: writeDiskBlockToDevice(int blockNumber, int writeFromAddress)");
			BlockWriteData blkWrDt = new BlockWriteData();
			blkWrDt.setBlockNumber(blockNumber);
			blkWrDt.setBlockAddress(writeFromAddress);

			BlockReadWriteDetails rwDt = this.systemSpaceMemManager.findReadWriteBlockDetails(blkWrDt);
			if (rwDt != null) {
				printLine("Info: Block Found In SystemSpace Memory Manager: " + writeFromAddress);
				int targetDiskBlockNumber = rwDt.getBlockWriteData().getBlockNumber();
				int targetDiskBlockAddress = rwDt.getBlockWriteData().getBlockAddress();

				this.simHW.store(Hardware.Address.diskBlockRegister, targetDiskBlockNumber);
				this.simHW.store(Hardware.Address.diskAddressRegister, targetDiskBlockAddress);
				this.simHW.store(Hardware.Address.diskCommandRegister, Hardware.Disk.writeCommand);
			} else {
				printLine("Info DeviceSpace: Block Not Found In SystemSpace Memory Manager: " + writeFromAddress);
			}
		}
	}

	/**
	 * Get the total program block count.
	 * @return
	 */
	private int getExeProgsBlockCount(){
		int blkCount = 0;		
		for ( int i = 0; i < Hardware.Disk.blockSize; i++ ){
			int programBlocks = simHW.fetch(Hardware.Address.userBase + i);//Find how many blocks executable programs occupy.
			if (programBlocks != 0){
				blkCount += programBlocks;
			}						
		}		
		return blkCount;
	}
	
	/**
	 * Get the total program block count.
	 * @return
	 */
	private int getExeProgramCount(){
		int count = 0;		
		for ( int i = 0; i < Hardware.Disk.blockSize; i++ ){
			int programBlocks = simHW.fetch(Hardware.Address.userBase + i);//Find how many blocks executable programs occupy.
			if (programBlocks != 0){
				count++; // increment count.
				
			}						
		}		
		return count;
	}	
	
	/**
	 * Operating system calls.
	 * @param sysCall
	 */
	public void operatingSystemCall(int sysCall) {		
		switch (sysCall) {
		case SystemCall.exec:			
			printLine("SystemCall: exec");	
			int progamID = this.simHW.fetch(Hardware.Address.systemBase + 1); // Get register 1.
			printLine("Program ID Before Execute:::::" + progamID);
			this.processProgramExeRequest(progamID);
			int processID = this.simHW.fetch(Hardware.Address.systemBase + 1); // Get register 1. 
			printLine("Process ID After Execute:::::" + processID);
			int processStatus = this.simHW.fetch(Hardware.Address.systemBase);
			
			if (processStatus == Hardware.Status.ok){
//				int parentProcessID = this.simHW.fetch(Hardware.Address.systemBase);
//				Process p = new Process(childProcessId, 0, 0 );
//				for (int i = 0; i <= 10; i++){
//					
//				}
				/*
				 The calling process requests that a new process start executing. 
				 Register 1 contains the word number of the batch-disk index block 
				 for the program associated with the new process. After 
				 the exec system call returns, the caller's register 0 contains the system call status
				 and register 1 contains the new process's id if the exec was successful; otherwise the contents of register 1 is undefined.
				  
				 The contents of registers 0 and 2 through 10 in the new process (the child process) are identical to the contents of the 
				 associated registers in the process that issued the exec (the parent process). Register 1 in the child process contains the parent process's id.   
				 */
				
				
				printLine("Execute::Hardware.Status.ok:::" + processStatus);
			} else if (processStatus == Hardware.Status.badPid){
				printLine("Execute::Hardware.Status.badPid:::" + processStatus);
			}  else if (processStatus == Hardware.Status.noResource){				
			} else {
				printLine("Execute::Hardware.Status.noResource:::" + processStatus);
			}			
			break;
		case SystemCall.exit:
			printLine("SystemCall: exit");			
			simHW.store(Hardware.Address.haltRegister, 2);
			break;		
		case SystemCall.getSlot:			
			printLine("SystemCall: getSlot");
			int beforeGetSlot = this.simHW.fetch(Hardware.Address.systemBase);
			printLine("beforeGetSlot: " + beforeGetSlot);
			int receiverID =  this.simHW.fetch(Hardware.Address.systemBase + 1); // Get register 1.
			int indexBlock = this.simHW.fetch(receiverID);
			
			int status = this.simHW.fetch(Hardware.Address.systemBase);
			printLine("status: " + status);
			
			receiverId = this.simHW.fetch(Hardware.Address.systemBase + 1);
			slotValue = this.simHW.fetch(Hardware.Address.systemBase + 2);
			printLine("w1: (receiverId) " + receiverId);			
			printLine("w2: (slotValue) " + slotValue);			
			this.simHW.store(Hardware.Address.systemBase + 1, receiverID);
			this.simHW.store(Hardware.Address.systemBase + 2, indexBlock);
			this.simHW.store(Hardware.Address.systemBase, Hardware.Status.ok);			
			break;		
		case SystemCall.putSlot:
			printLine("SystemCall: putSlot");			
			this.simHW.store(Hardware.Address.systemBase, Hardware.Status.ok);
			this.simHW.store(Hardware.Address.systemBase + 1, slotValue);
			this.simHW.store(Hardware.Address.systemBase + 2, receiverId);			
					
			break;		
		case SystemCall.yield:
			printLine("SystemCall: yield");			
			this.simHW.store(Hardware.Address.systemBase, Hardware.Status.ok);
			break;
		case SystemCall.open:
			printLine("SystemCall: open");
			int openDeviceStatus = this.simHW.fetch(Hardware.Address.systemBase);
			printLine("Current Device Status: " + openDeviceStatus);
			int deviceType = this.simHW.fetch(Hardware.Address.systemBase + 1); // Word 1 (1 is drive, 3 is terminal)
			
			terminalInUse = terminalStack.contains(deviceType); // Is the terminal being used?
			printLine("terminalInUse: " + terminalInUse);
			
			if ((deviceType == Hardware.Terminal.device || deviceType == Hardware.Disk.device)) {
				if (deviceType == Hardware.Disk.device){					
					/*
					 * Triggers the call.
					 */
					this.simHW.store(Hardware.Address.systemBase, Hardware.Status.ok);
					int connectionId = this.simHW.fetch(Hardware.Address.systemBase + 1);
					
					int hwStatus = this.simHW.fetch(Hardware.Address.systemBase);					
					if (hwStatus == Hardware.Status.ok) {
						int getDeviceStatus = this.simHW.fetch(Hardware.Address.systemBase);
						/*
						 *Device is OK
						 */
						if (getDeviceStatus == Hardware.Status.ok) { 
							ConnectionDetails cnIdInfo = new ConnectionDetails();
							/*
							 * Get and set the connection ID
							 */
							cnIdInfo.setConnectionID(connectionId);
							/*
							 * Connection was used to open it.
							 */
							cnIdInfo.setConnectionOpen(true); 
							cnIdInfo.setDeviceType(deviceType);
							/*
							 * Save connection id after the call within the list.
							 */
							connectionList.add(cnIdInfo);							
							
						} else if (getDeviceStatus == Hardware.Status.badDevice) {
							printLine("getDeviceStatus == Hardware.Status.badDevice");
						}
					} else if (hwStatus == Hardware.Status.deviceBusy) {
						printLine("getDeviceStatus == Hardware.Status.deviceBusy");
					} else if (hwStatus == Hardware.Status.badDevice) {
						printLine("getDeviceStatus == Hardware.Status.badDevice");
					}
					
				} else if (deviceType == Hardware.Terminal.device) {
					/*
					 * Triggers the call.
					 */
					this.simHW.store(Hardware.Address.systemBase, Hardware.Status.ok);

					int terminalHWStatus = this.simHW.fetch(Hardware.Address.systemBase);
					if (terminalHWStatus == Hardware.Status.ok) {
						
						ConnectionDetails cnIdInfo = new ConnectionDetails();
						/*
						 * Get and set the connection ID
						 */
						int connectionId = this.simHW.fetch(Hardware.Address.systemBase + 1);
						cnIdInfo.setConnectionID(connectionId);
						/*
						 * Connection was used to open it.
						 */
						cnIdInfo.setConnectionOpen(true);
						cnIdInfo.setDeviceType(deviceType);
						/*
						 * Save connection id after the call within the list.
						 */
						connectionList.add(cnIdInfo);
					} else if (terminalHWStatus == Hardware.Status.deviceBusy) {
						printLine("Hardware.Status.deviceBusy");
					} else if (terminalHWStatus == Hardware.Status.badDevice) {
						printLine("Hardware.Status.badDevice");
					} else {
						printLine("Terminal: Undefined...");
					}
				}
			}
			break;
		case SystemCall.close:
			printLine("SystemCall: close");
			int closeConnectionID = this.simHW.fetch(Hardware.Address.systemBase + 1); // Word 1 (1 is drive, 3 is terminal)
			printLine("cnnID: " + closeConnectionID);
			this.simHW.store(Hardware.Address.systemBase, Hardware.Status.ok);			
			int closeDeviceStatus = this.simHW.fetch(Hardware.Address.systemBase);
			
			printLine("closeDeviceStatus: " + closeDeviceStatus);
			
			if (closeDeviceStatus == Hardware.Status.ok) {
				for (ConnectionDetails cnDetail : connectionList) {
					if (cnDetail.getConnectionID() == closeConnectionID) {
						cnDetail.setConnectionClose(true);
						cnDetail.setConnectionIDWasUsed(true);
					}
				}
			} else if (closeDeviceStatus == Hardware.Status.badDevice) {
				printLine(" Hardware.Status.badDevice");
			}					
			break;
		case SystemCall.read:
			printLine("SystemCall: read");
			int readConectionID = this.simHW.fetch(Hardware.Address.systemBase + 1); // Word 1 (1 is drive, 3 is terminal)
			int beforeCallReadReadToAddress = this.simHW.fetch(Hardware.Address.systemBase + 2);
			int beforeCallReadNValue = this.simHW.fetch(Hardware.Address.systemBase + 3);
			/*
			 * This is what will trigger the call.
			 */
			this.simHW.store(Hardware.Address.systemBase, Hardware.Status.ok);
			int deviceReadStatus = this.simHW.fetch(Hardware.Address.systemBase);
			
			ConnectionDetails connectionDetails = new ConnectionDetails();
			for (ConnectionDetails cnDetail : connectionList) {
				if (cnDetail.getConnectionID() == readConectionID && cnDetail.isConnectionIDWasUsed() == false) {
					connectionDetails = cnDetail;
				}
			}
			
			int readDeviceType = connectionDetails.getDeviceType();
			
			if (deviceReadStatus == Hardware.Status.ok) {
				int afterCallReadNValue = this.simHW.fetch(Hardware.Address.systemBase + 1);
				if ((readDeviceType == Hardware.Terminal.device || readDeviceType == Hardware.Disk.device)) {
					if (readDeviceType == Hardware.Disk.device){						
					   
						if (afterCallReadNValue > 0){
							if (beforeCallReadReadToAddress > 0){								
								this.readDiskBlockFromDevice(beforeCallReadNValue, beforeCallReadReadToAddress);	
								printLine("executeDeviceReadCall->Disk writeFromAddres: Word 2: " + beforeCallReadReadToAddress);
							    printLine("executeDeviceReadCall->Disk nValue: Word 3 (char count): " + afterCallReadNValue);
							} 
						} else {
							this.simHW.store(Hardware.Address.systemBase, Hardware.Status.badCount);
						}
						
					} else if (readDeviceType == Hardware.Terminal.device) {
						terminalDataStartAddress = beforeCallReadReadToAddress;						
						numberOfCharToRead = afterCallReadNValue;
						printLine("executeDeviceReadCall->Terminal (nValue): Word 3: " + afterCallReadNValue);
						
						if (afterCallReadNValue > 0){
							if (beforeCallReadReadToAddress > 0){
								if (ttyData == Hardware.Terminal.eosCharacter) {
									printLine("Hardware.Terminal.eosCharacter: " + Hardware.Terminal.eosCharacter);
									this.simHW.store(Hardware.Address.systemBase + 1, 0);	
								} else {
									this.simHW.store(Hardware.Address.terminalCommandRegister,  Hardware.Terminal.readCommand);				
									// this.simHW.store(Hardware.Address.systemBase + 1, beforeCallReadNValue);				
								}	
							} 
						} else {
							this.simHW.store(Hardware.Address.systemBase, Hardware.Status.badCount);
						}											
					}					
				} 				
			} else if (deviceReadStatus == Hardware.Status.badDevice) {
				
			} else if (deviceReadStatus == Hardware.Status.badAddress) {
				
			} else if (deviceReadStatus == Hardware.Status.badCount) {
				
			}			
			
			break;
		case SystemCall.write:
			printLine("SystemCall: write");
			int beforeWriteConnectionID = this.simHW.fetch(Hardware.Address.systemBase + 1);
			int beforeWriteWriteFromAddress = this.simHW.fetch(Hardware.Address.systemBase + 2);
			int beforeWriteNValue = this.simHW.fetch(Hardware.Address.systemBase + 3);
			/*
			 * This is what will trigger the call.
			 */
			this.simHW.store(Hardware.Address.systemBase, Hardware.Status.ok);
			
			ConnectionDetails writeConnectionDetails = new ConnectionDetails();
			for (ConnectionDetails cnDetail : connectionList) {
				if (cnDetail.getConnectionID() == beforeWriteConnectionID && cnDetail.isConnectionIDWasUsed() == false) {
					writeConnectionDetails = cnDetail;
				}
			}
			
			int writeDeviceType = writeConnectionDetails.getDeviceType();
			int deviceWriteStatus = this.simHW.fetch(Hardware.Address.systemBase);
			if (deviceWriteStatus == Hardware.Status.ok) {				
				
				// int writeDeviceStatus = this.simHW.fetch(Hardware.Address.systemBase);				
				
				if ((writeDeviceType == Hardware.Terminal.device || writeDeviceType == Hardware.Disk.device)) {
					this.simHW.store(Hardware.Address.systemBase, Hardware.Status.ok);
					if (writeDeviceType == Hardware.Disk.device){
						
						int writeFromAddress = this.simHW.fetch(Hardware.Address.systemBase + 2); // Word 2
						printLine("eDeviceWriteCall->Disk writeFromAddres: Word 2: " + writeFromAddress);
					
						int nValue = this.simHW.fetch(Hardware.Address.systemBase + 3); // Word 3
						printLine("eDeviceWriteCall->Disk nValue: Word 3: " + nValue);			
						
						if (nValue > 0){
							if (writeFromAddress > 0) {					
								this.writeDiskBlockToDevice(nValue, writeFromAddress);
							}  
						} else {
							this.simHW.store(Hardware.Address.systemBase, Hardware.Status.badBlockNumber);
						}			
				
					} else if (writeDeviceType == Hardware.Terminal.device) {						
						printLine("executeDeviceReadCall->Terminal writeFromAddress: Word 2: " + beforeWriteWriteFromAddress);
								
						int nValue = this.simHW.fetch(Hardware.Address.systemBase + 3); // Word 3
						printLine("executeDeviceReadCall->Terminal nValue: Word 3: " + nValue);			
									
						if (ttyData != Hardware.Terminal.eosCharacter) {
							this.simHW.store(Hardware.Address.terminalCommandRegister,  Hardware.Terminal.writeCommand);	
						}
					} 
				} else {
					this.simHW.store(Hardware.Address.systemBase, Hardware.Status.badDevice);
				}			
				
			} else if (deviceWriteStatus == Hardware.Status.badDevice) {
				
			} else if (deviceWriteStatus == Hardware.Status.badAddress) {
				
			} else if (deviceWriteStatus == Hardware.Status.badCount) {
				
			}					
			break;
		}
	}
	
	
	/**
	 * Creates the disk blocks to reference for referencing program block information
	 */
	private void createDiskEntity(){		
		List<WordEntity> wordEnt = new ArrayList<WordEntity>();
		
		for (int i = Hardware.Address.userBase; i < Hardware.Address.userTop - 1; i++) // Create a list of word entities.
		{
			WordEntity wEnt = new WordEntity();				
			wEnt.setWordAddress(i); // Add the word address.
			wordEnt.add(wEnt);			
		}			
		
		List<WordEntity> wordList = Collections.unmodifiableList(wordEnt);
		List<List<WordEntity>> subParts = createBlockList(wordList, 32);
		
		for (int i = 0; i < subParts.size(); i++)
		{
			BlockEntity blkEnt = new BlockEntity();
			blkEnt.setWordEntityList(subParts.get(i));
			dEnt.getBlockEntityList().add(blkEnt);
		}				
	}
	
	/**
	 * Create block list.
	 * @param list
	 * @param L
	 * @return
	 */
	private static <T> List<List<T>> createBlockList(List<T> list, final int L) {
		List<List<T>> parts = new ArrayList<List<T>>();
		final int N = list.size();
		for (int i = 0; i < N; i += L) {
			parts.add(new ArrayList<T>(list.subList(i, Math.min(N, i + L))));
		}
		return parts;
	}

	/**
	 * Shows how to get to the first and last block of each process based on the index block(0).
	 * @param nBlock
	 */
	private void queueProcessExecution(List<WordEntity> iBlock){
		int currentProcessFirstBlock = 0;			
		for (int i = 0; i < iBlock.size(); i++){
			int proAddr = iBlock.get(i).getWordAddress(); // Block count for the first program.
			int proBlock = this.simHW.fetch(proAddr); // Get the block count for the first program.						
			if (proBlock != 0) {
				int firstBlk = currentProcessFirstBlock + 1;
				int processStartBlockAddress = dEnt.getBlockEntityList().get(firstBlk).getWordEntityList().get(0).getWordAddress(); // First block for the process.
				int lastBlk = currentProcessFirstBlock + proBlock;
				int processEndBlockAddress = dEnt.getBlockEntityList().get(lastBlk).getWordEntityList().get(31).getWordAddress(); // Last block for the process.
				
				/*
				 *  The hardware interprets the contents of the Base Register 
				 *  as the lowest legal address accessible to the currently running process.
				 */
				int baseRegister = processStartBlockAddress;  
				
				/*
				 * The hardware interprets the contents of the Top Register 
				 * as one more than the highest legal address accessible to the currently running process.
				 */
				int topRegister = processEndBlockAddress + 1;
				
				/*
				 *  The hardware interprets the contents of the Program Counter Register 
				 *  as the address of the next instruction to execute.
				 */
				int pCRegister = processStartBlockAddress;  
								
				printLine("queueProcessExecution.pCRegister: " + pCRegister);
				printLine("queueProcessExecution.baseRegister: " + baseRegister);
				printLine("queueProcessExecution.topRegister: " + topRegister);
				printLine("");
								
				// Set the program block list to allow to iterate using a preemptive round robin scheduling scheme base on a count down.
				this.setProgramBlockList(processStartBlockAddress, proBlock, pCRegister, baseRegister, topRegister);
								
				currentProcessFirstBlock += proBlock; // Update the program start to the next block after the current process last block.				
			}			
		}				
	}
	
	/**
	 * Creates a list containing all of the blocks within one program.  This allows preemptive round robin execution.
	 * @param programStartBlock
	 * @param processBlockCount
	 * @param pCRegister
	 * @param baseRegister
	 * @param topRegister
	 */
	private void setProgramBlockList(int programStartBlock, int processBlockCount, int pCRegister, int baseRegister, int topRegister){
		BlockEntity bEnt = new BlockEntity();
		for (int proBlockIndex = programStartBlock; proBlockIndex <= processBlockCount; proBlockIndex++){ // Go through the addresses in block range.
			List<WordEntity> iBlock = dEnt.getBlockEntityList().get(proBlockIndex).getWordEntityList(); // Get the block range based on start and end blocks.
			for (int j = 0; j < iBlock.size(); j ++) {
				bEnt.getWordEntityList().add(iBlock.get(j));				
			}			
		}
		bEnt.setBaseRegister(baseRegister); // Set the register per block.
		bEnt.setTopRegister(topRegister); // 
		bEnt.setPCRegister(pCRegister);
		proEnt.getBlockEntityList().add(bEnt);
	}
	
	/**
	 * Process a program based on the range found in a program.
	 * @param index
	 */
	private void processProgramExeRequest(int index) {
		int progCount = this.getExeProgramCount();
		if (index < progCount){
			
			printLine("Program Index: " + index);
			
			int pCRegister = proEnt.getBlockEntityList().get(index).getPCRegister();
			int baseRegister = proEnt.getBlockEntityList().get(index).getBaseRegister();
			int topRegister = proEnt.getBlockEntityList().get(index).getTopRegister();
			
			printLine("pCRegister: " + pCRegister);
			printLine("baseRegister: " + baseRegister);
			printLine("topRegister: " + topRegister);
			
			this.simHW.store(Hardware.Address.PCRegister, pCRegister); // Address of the next program to execute.
			this.simHW.store(Hardware.Address.baseRegister, baseRegister); // Lowest legal address accessible to the current running process.
			this.simHW.store(Hardware.Address.topRegister, topRegister); // One more than the highest legal address.

		} else {
			simHW.store(Hardware.Address.haltRegister, 2);
		}			
	}
		
	private static void printLine(String msg){
		System.out.println(msg);		
	}
}